{
  "cells": [
    {
      "cell_type": "markdown",
      "metadata": {},
      "source": []
    },
    {
      "attachments": {},
      "cell_type": "markdown",
      "metadata": {
        "id": "F5VPsX1Lixae"
      },
      "source": [
        "# About Customize Demo\n",
        "Module is the core function of the BMF framework, which is the essential instance for executing logic. Users first create a BMF Graph and combine the logic of multiple Modules through serial connection to generate a description graph of the audio and video processing pipeline. When the user executes graph.Run() or graph.start(), the graph description produced by the user will be mapped as a real \"executing graph\" at the Engine layer. This graph is the real instance for executing the Module processing logic, which is composed of multiple Nodes combined in series, and each Node is bound to a Module instance at the bottom.\n",
        "\n",
        "The Scheduler in the BMF Engine framework is responsible for scheduling each Node. When constructing a Module instance, the program first reads the metadata of the Module, which defines the type, path, entry, and other information of the Module. After obtaining this information, the program uses a singleton, ModuleManager, to construct the Module instance in reality (by loading it using the corresponding language's loader) and provide it to the Node for use.\n",
        "\n",
        "For a Module, there are three methods to be implemented mustly: init(), process(), and close(). When the Node is scheduled by the Scheduler, these methods will be executed in turn until the lifecycle ends. The architecture described above can be represented by the following diagram:\n",
        "\n",
        "![customize_module.drawio.png]()\n",
        "\n",
        "This notebook will show you how to develop Customize Python/C++ Module through the BMF framework as a module developer.Here are the codes with detailed comments:"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "MjaNzZ3fse4C"
      },
      "source": [
        "# 1. Install BMF in Python environment."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "71DnSUiI28te"
      },
      "outputs": [],
      "source": [
        "!pip install BabitMF"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "UNW9hqaHq_im"
      },
      "source": [
        "### Configure the environment variable to reference the BMF C library"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "mg1rFxJTx9EO"
      },
      "source": [
        "Set necessary environment, using `bmf_env`, this step will print some necessary environment variables which is needed for c++/go development, or you intend to use the `module_manager` tool:"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "TndM3e0Bq1DP"
      },
      "outputs": [],
      "source": [
        "! which bmf_env\n",
        "! cat /usr/local/bin/bmf_env\n",
        "! bmf_env"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "9H4sBMA0zZEG"
      },
      "source": [
        "Copy and execute it in your terminal, in colab, we use `%env`:"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "5vpa1Lhsq3X9"
      },
      "outputs": [],
      "source": [
        "%env C_INCLUDE_PATH=/usr/local/lib/python3.10/dist-packages/bmf/include\n",
        "%env CPLUS_INCLUDE_PATH=/usr/local/lib/python3.10/dist-packages/bmf/include\n",
        "%env LIBRARY_PATH=/usr/local/cuda/lib64/stubs:/usr/local/lib/python3.10/dist-packages/bmf/lib\n",
        "%env LD_LIBRARY_PATH=/usr/local/nvidia/lib:/usr/local/nvidia/lib64:/usr/local/lib/python3.10/dist-packages/bmf/lib"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "MtkenCr7syuG"
      },
      "source": [
        "## 2.The Engine layer of the BMF framework is uniformly implemented in C++ language. In Colab, when python calls the C++ library, the log of the C++ library layer will be hidden, so it is necessary to install and load the wurlitezer library to enable logs in the C++ layer."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "O1lA6mqGIC5m"
      },
      "outputs": [],
      "source": [
        "!pip install wurlitzer\n",
        "%load_ext wurlitzer"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "gt41T_YXt_wk"
      },
      "source": [
        "## 3. Golang is needed when you are ready to develop a go module."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "ikYokZjduLSi"
      },
      "outputs": [],
      "source": [
        "! apt install golang"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "3nyirq96tBuA"
      },
      "source": [
        "## 4. Download a sample transcoded video from our assets (using Big Bunny as an example here)."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "GU5LOzPXE1GW"
      },
      "outputs": [],
      "source": [
        "!wget https://github.com/BabitMF/bmf/releases/download/files/files.tar.gz\n",
        "!tar -zvxf files.tar.gz"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "wEYaqf_ct5Oa"
      },
      "source": [
        "## 5. Write and implement Customize Module Demo\n",
        "### Overall, Customize Module Demo can be decomposed into three sub-processes:\n",
        "### 1. Implement the Customize Module used Python.\n",
        "### 2. Implement the Customize Module used C++.\n",
        "### 3. Implement the Customize Module used go.\n",
        "We will analyze in detail the above two processes one by one."
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "S10idpjhuhzc"
      },
      "source": [
        "### Implement the Customize Module used Python\n",
        "The following code implements a simple Python module, named my_module. In the process method of my_module, it only does one thing: get a packet from the InputQueue, and then push it into the OutputQueue. Essentially, this is a pass-through module.\n"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "BW1-HOeW0Vfa"
      },
      "source": [
        "Create a directory named `my_python_module` to store our c++ module:"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "zCf8dUS-0MkA"
      },
      "outputs": [],
      "source": [
        "! mkdir my_python_module"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "hVZ8L3-dIHH2"
      },
      "outputs": [],
      "source": [
        "%%writefile my_python_module/my_module.py\n",
        "from bmf import Module, Log, LogLevel, InputType, ProcessResult, Packet, Timestamp, scale_av_pts, av_time_base, \\\n",
        "    BmfCallBackType, VideoFrame, AudioFrame\n",
        "\n",
        "\n",
        "class my_module(Module):\n",
        "    def __init__(self, node, option=None):\n",
        "        self.node_ = node\n",
        "        self.option_ = option\n",
        "        pass\n",
        "\n",
        "    def process(self, task):\n",
        "        for (input_id, input_packets) in task.get_inputs().items():\n",
        "\n",
        "            # output queue\n",
        "            output_packets = task.get_outputs()[input_id]\n",
        "\n",
        "            while not input_packets.empty():\n",
        "                pkt = input_packets.get()\n",
        "\n",
        "                # process EOS\n",
        "                if pkt.timestamp == Timestamp.EOF:\n",
        "                    Log.log_node(LogLevel.DEBUG, task.get_node(), \"Receive EOF\")\n",
        "                    output_packets.put(Packet.generate_eof_packet())\n",
        "                    task.timestamp = Timestamp.DONE\n",
        "                    return ProcessResult.OK\n",
        "\n",
        "                # copy input packet to output\n",
        "                if pkt.defined() and pkt.timestamp != Timestamp.UNSET:\n",
        "                    output_packets.put(pkt)\n",
        "                    # Log.log_node(LogLevel.DEBUG, self.node_,\n",
        "                    #              \"process input\", input_id, 'packet',\n",
        "                    #              output_packets.queue[0].get_timestamp())\n",
        "\n",
        "        return ProcessResult.OK\n"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "TH2IETuWvLAe"
      },
      "source": [
        "Install this module using `module_manager` provideed by BMF:"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "rQUXUOR4vXch"
      },
      "outputs": [],
      "source": [
        "! module_manager install my_python_module python my_module:my_module $(pwd)/my_python_module v0.0.1"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "xGGKS9R_1VRz"
      },
      "source": [
        "You can dump the module info:"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "N8O4QC261TIU"
      },
      "outputs": [],
      "source": [
        "! module_manager dump my_python_module"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "fu9atAlyxizF"
      },
      "source": [
        "Let's implement a BMF Graph in which we use the module interface to call the my_module we just completed. We set the entry as \\_\\_main\\_\\_.my_module because all Python code runs in \\_\\_main\\_\\_ in Google Colab."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "vM3JZjmXHO-k"
      },
      "outputs": [],
      "source": [
        "import bmf\n",
        "input_video_path = \"./files/big_bunny_10s_30fps.mp4\"\n",
        "output_path = \"./custom_module.mp4\"\n",
        "\n",
        "(\n",
        "    bmf.graph()\n",
        "        .decode({'input_path': input_video_path})['video']\n",
        "        .module('my_python_module')\n",
        "        .encode(None, {\"output_path\": output_path})\n",
        "        .run()\n",
        ")"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "hAH2caJIiEG0"
      },
      "source": [
        "### Implement the Customize Module used C++\n",
        "The following code implements a simple C++ module, named cpp_copy_module. Unlike the above Python module, copy_module will make a copy of the Input Packet and then push it to the OutputQueue. Compared with my_module, copy_module will do one more copy.\n",
        "\n",
        "To implement a C++ Module, there are several additional steps that need to be taken. First, download and install the BMF Library. Then, implement the module and CMake File separately to complete the construction of the C++ Module. Finally, a compiled product (.so) will be produced."
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "qmVt0gU7FvVA"
      },
      "source": [
        "Create a directory named `cpp_copy_module` to store our c++ module:"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "9DCufzhHF2ww"
      },
      "outputs": [],
      "source": [
        "! mkdir cpp_copy_module"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "WEpO79yNNVlI"
      },
      "outputs": [],
      "source": [
        "%%writefile /content/cpp_copy_module/copy_module.h\n",
        "#ifndef BMF_COPY_MODULE_H\n",
        "#define BMF_COPY_MODULE_H\n",
        "\n",
        "#include <bmf/sdk/bmf.h>\n",
        "#include <bmf/sdk/packet.h>\n",
        "\n",
        "USE_BMF_SDK_NS\n",
        "\n",
        "class CopyModule : public Module\n",
        "{\n",
        "public:\n",
        "    CopyModule(int node_id,JsonParam option) : Module(node_id,option) { }\n",
        "\n",
        "    ~CopyModule() { }\n",
        "\n",
        "    virtual int process(Task &task);\n",
        "};\n",
        "\n",
        "#endif\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "ys11zw8A5q4t"
      },
      "outputs": [],
      "source": [
        "%%writefile /content/cpp_copy_module/copy_module.cc\n",
        "#include \"copy_module.h\"\n",
        "\n",
        "int CopyModule::process(Task &task) {\n",
        "    PacketQueueMap &input_queue_map = task.get_inputs();\n",
        "    PacketQueueMap::iterator it;\n",
        "\n",
        "    // process all input queues\n",
        "    for (it = input_queue_map.begin(); it != input_queue_map.end(); it++) {\n",
        "        // input stream label\n",
        "        int label = it->first;\n",
        "\n",
        "        // input packet queue\n",
        "        Packet pkt;\n",
        "        // process all packets in one input queue\n",
        "        while (task.pop_packet_from_input_queue(label, pkt)) {\n",
        "            // Get a input packet\n",
        "\n",
        "            // if packet is eof, set module done\n",
        "            if (pkt.timestamp() == BMF_EOF) {\n",
        "                task.set_timestamp(DONE);\n",
        "                task.fill_output_packet(label, Packet::generate_eof_packet());\n",
        "                return 0;\n",
        "            }\n",
        "\n",
        "            // Get packet data\n",
        "            // Here we should know the data type in packet\n",
        "            auto vframe = pkt.get<VideoFrame>();\n",
        "\n",
        "            // Deep copy\n",
        "            VideoFrame vframe_out = VideoFrame(vframe.frame().clone());\n",
        "            vframe_out.copy_props(vframe);\n",
        "\n",
        "            // Add output frame to output queue\n",
        "            auto output_pkt = Packet(vframe_out);\n",
        "\n",
        "            task.fill_output_packet(label, output_pkt);\n",
        "        }\n",
        "    }\n",
        "    return 0;\n",
        "}\n",
        "REGISTER_MODULE_CLASS(CopyModule)"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "zP7eusd_69Zb"
      },
      "outputs": [],
      "source": [
        "%%writefile /content/cpp_copy_module/CMakeLists.txt\n",
        "file(GLOB SRCS *.cc *.h)\n",
        "\n",
        "add_library(copy_module SHARED ${SRCS})\n",
        "set_property(TARGET PROPERTY CXX_STANDARD 17)\n",
        "\n",
        "add_definitions(-D_GLIBCXX_USE_CXX11_ABI=0)\n",
        "\n",
        "target_link_libraries(copy_module\n",
        "    PRIVATE\n",
        "    bmf_module_sdk\n",
        ")\n",
        "\n",
        "set(CMAKE_INSTALL_PREFIX ${PROJECT_SOURCE_DIR})\n",
        "install(TARGETS copy_module)"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "ic8hZMFE8A37"
      },
      "outputs": [],
      "source": [
        "!if [ -d build ]; then rm -rf build; fi\n",
        "!cmake -DCMAKE_CXX_STANDARD=17 -B build -S cpp_copy_module\n",
        "!cmake --build build\n",
        "!cmake --install build"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "kx7Y3QY4rY5j"
      },
      "outputs": [],
      "source": [
        "!stat cpp_copy_module/lib/libcopy_module.so"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "WbYfLFAOGvZQ"
      },
      "source": [
        "Installing the module using `module_manager`:"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "XjzawgVvGzvz"
      },
      "outputs": [],
      "source": [
        "! module_manager install cpp_copy_module c++ libcopy_module:CopyModule $(pwd)/cpp_copy_module/lib v0.0.1"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "0pcZrmys-mrv"
      },
      "outputs": [],
      "source": [
        "import bmf\n",
        "input_video_path = \"./files/big_bunny_10s_30fps.mp4\"\n",
        "output_path = \"./custom_module_c.mp4\"\n",
        "\n",
        "# decode\n",
        "video = bmf.graph().decode({'input_path': input_video_path})\n",
        "# c module processing\n",
        "video_2 = (\n",
        "    video['video'].c_module(\"cpp_copy_module\")\n",
        ")\n",
        "\n",
        "# encode\n",
        "(\n",
        "    bmf.encode(\n",
        "        video_2,  # video stream, set to None\n",
        "        video['audio'],\n",
        "        {\"output_path\": output_path,\n",
        "          \"video_params\": {\n",
        "              \"vsync\": \"vfr\",\n",
        "              \"max_fr\": 60\n",
        "          },\n",
        "          \"audio_params\": {\"codec\": \"aac\"}\n",
        "          }\n",
        "    ).run()\n",
        ")"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "_pfTeeqXJJlf"
      },
      "source": [
        "### Implement the Customize Module used go\n",
        "The following code implements a simple go module, named pass_through. like c++, pass_through will make a copy of the Input Packet and then push it to the OutputQueue. Compared with my_module, copy_module will do one more copy.\n",
        "\n",
        "To implement a go Module, there are several additional steps that need to be taken. First, download and install the BMF Library. Then, implement the module and a compiled product (.so) will be produced."
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "1eZWqYGkKcgf"
      },
      "source": [
        "Create a directory named `pass_through_module` to store our go module, and `pass_through_module/lib` to store the .so file:"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "O3Xl3zBgKWfo"
      },
      "outputs": [],
      "source": [
        "! mkdir -p pass_through_module/lib"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "_G_nKiEcKR69"
      },
      "outputs": [],
      "source": [
        "%%writefile pass_through_module/pass_through.go\n",
        "\n",
        "package main\n",
        "\n",
        "import \"C\"\n",
        "import (\n",
        "        \"encoding/json\"\n",
        "        \"errors\"\n",
        "        \"fmt\"\n",
        "\n",
        "        \"github.com/babitmf/bmf-gosdk/bmf\"\n",
        ")\n",
        "\n",
        "type PassThroughModuleOption struct {\n",
        "        Value int32\n",
        "}\n",
        "\n",
        "type PassThroughModule struct {\n",
        "        nodeId int32\n",
        "        option PassThroughModuleOption\n",
        "}\n",
        "\n",
        "func (self *PassThroughModule) Process(task *bmf.Task) error {\n",
        "        fmt.Println(\"Go-PassThrough process-in\")\n",
        "        defer fmt.Println(\"Go-PassThrough process-out\")\n",
        "        iids := task.GetInputStreamIds()\n",
        "        oids := task.GetOutputStreamIds()\n",
        "\n",
        "        gotEof := false\n",
        "        for i, iid := range iids {\n",
        "                for pkt, err := task.PopPacketFromInputQueue(iid); err == nil; {\n",
        "                        defer pkt.Free()\n",
        "                        if ok := task.FillOutputPacket(oids[i], pkt); !ok {\n",
        "                                return errors.New(\"Fill output queue failed\")\n",
        "                        }\n",
        "\n",
        "                        if pkt.Timestamp() == bmf.EOF {\n",
        "                                gotEof = true\n",
        "                        }\n",
        "\n",
        "                        pkt, err = task.PopPacketFromInputQueue(iid)\n",
        "                }\n",
        "        }\n",
        "\n",
        "        if gotEof {\n",
        "                task.SetTimestamp(bmf.DONE)\n",
        "        }\n",
        "        return nil\n",
        "}\n",
        "\n",
        "func (self *PassThroughModule) Init() error {\n",
        "        return nil\n",
        "}\n",
        "\n",
        "func (self *PassThroughModule) Reset() error {\n",
        "        return errors.New(\"Reset is not supported\")\n",
        "}\n",
        "\n",
        "func (self *PassThroughModule) Close() error {\n",
        "        return nil\n",
        "}\n",
        "\n",
        "func (self *PassThroughModule) GetModuleInfo() (interface{}, error) {\n",
        "        info := map[string]string{\n",
        "                \"NodeId\": fmt.Sprintf(\"%d\", self.nodeId),\n",
        "        }\n",
        "\n",
        "        return info, nil\n",
        "}\n",
        "\n",
        "func (self *PassThroughModule) NeedHungryCheck(istreamId int32) (bool, error) {\n",
        "        return true, nil\n",
        "}\n",
        "\n",
        "func (self *PassThroughModule) IsHungry(istreamId int32) (bool, error) {\n",
        "        return true, nil\n",
        "}\n",
        "\n",
        "func (self *PassThroughModule) IsInfinity() (bool, error) {\n",
        "        return true, nil\n",
        "}\n",
        "\n",
        "func NewPassThroughModule(nodeId int32, option []byte) (bmf.Module, error) {\n",
        "        m := &PassThroughModule{}\n",
        "        err := json.Unmarshal(option, &m.option)\n",
        "        if err != nil {\n",
        "                return nil, err\n",
        "        }\n",
        "        m.nodeId = nodeId\n",
        "\n",
        "        return m, nil\n",
        "}\n",
        "\n",
        "func RegisterPassThroughInfo(info bmf.ModuleInfo) {\n",
        "        info.SetModuleDescription(\"Go PassThrough description\")\n",
        "        tag := bmf.NewModuleTag(bmf.BMF_TAG_UTILS|bmf.BMF_TAG_VIDEO_PROCESSOR)\n",
        "        info.SetModuleTag(tag)\n",
        "}\n",
        "\n",
        "//export ConstructorRegister\n",
        "func ConstructorRegister() {\n",
        "        bmf.RegisterModuleConstructor(\"go_pass_through\", NewPassThroughModule, RegisterPassThroughInfo)\n",
        "}\n",
        "\n",
        "func main() {}"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "4yWuD5fCKt5C"
      },
      "outputs": [],
      "source": [
        "! go mod init test\n",
        "! go mod tidy\n",
        "! go build -buildmode c-shared -o pass_through_module/lib/go_pass_through.so pass_through_module/pass_through.go"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "IkH6hAUhLIYk"
      },
      "source": [
        "Installing the module using `module_manager`:"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "uZrrwc3GLHwg"
      },
      "outputs": [],
      "source": [
        "! module_manager install go_pass_through go go_pass_through:PassThrough $(pwd)/pass_through_module/lib v0.0.1"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "xJYRM2TSQ0L5"
      },
      "outputs": [],
      "source": [
        "import bmf\n",
        "input_video_path = \"./files/big_bunny_10s_30fps.mp4\"\n",
        "output_path = \"./custom_module_go.mp4\"\n",
        "\n",
        "# decode\n",
        "video = bmf.graph().decode({'input_path': input_video_path})\n",
        "# c module processing\n",
        "video_2 = (\n",
        "    video['video'].go_module(\"go_pass_through\")\n",
        ")\n",
        "\n",
        "# encode\n",
        "(\n",
        "    bmf.encode(\n",
        "        video_2,  # video stream, set to None\n",
        "        video['audio'],\n",
        "        {\"output_path\": output_path,\n",
        "          \"video_params\": {\n",
        "              \"vsync\": \"vfr\",\n",
        "              \"max_fr\": 60\n",
        "          },\n",
        "          \"audio_params\": {\"codec\": \"aac\"}\n",
        "          }\n",
        "    ).run()\n",
        ")"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "nzn35ZPZvhyR"
      },
      "source": [
        "## 6. Display the video streams before and after processing."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "zACHQOdMvbEZ"
      },
      "outputs": [],
      "source": [
        "from IPython.display import HTML\n",
        "from base64 import b64encode\n",
        "\n",
        "def show_video(video_path, video_width = 800):\n",
        "  video_file = open(video_path, \"r+b\").read()\n",
        "  video_url = f\"data:video/mp4;base64,{b64encode(video_file).decode()}\"\n",
        "  return f\"\"\"\n",
        "  <video width={video_width} controls>\n",
        "    <source src=\"{video_url}\">\n",
        "  </video>\n",
        "  \"\"\"\n",
        "\n",
        "video_url1 = show_video('./files/big_bunny_10s_30fps.mp4')\n",
        "video_url2 = show_video('custom_module.mp4')\n",
        "video_url3 = show_video('custom_module_c.mp4')\n",
        "video_url4 = show_video('custom_module_go.mp4')\n",
        "\n",
        "html = video_url1 + video_url2 + video_url3 + video_url4\n",
        "HTML(html)"
      ]
    }
  ],
  "metadata": {
    "colab": {
      "provenance": [],
      "toc_visible": true
    },
    "kernelspec": {
      "display_name": "Python 3",
      "name": "python3"
    },
    "language_info": {
      "name": "python"
    }
  },
  "nbformat": 4,
  "nbformat_minor": 0
}
